from functools import wraps
from inspect import isfunction, signature, Parameter
from typing import Callable, Optional, Tuple, Union


def decorate_fn(invoke: Callable, init: Optional[Callable] = None) -> Callable:
    """输出装载了invoke和init方法的闭包函数，包装需要使用invoke和init方法的函数。
    可以单独作为装饰器使用。

    Args:
        invoke (Callable): 调用方法，即环切拦截器
        init (Callable, optional): 初始化方法，可以不传入

    Returns:
        init_fn (Callable): 闭包内部函数，传入方法指针即可启用invoke和init方法

    Example::

        from typing import Callable
        from decorator.func_helper import decorate_fn

        def hello_init(func: Callable):
            print("function 'hello' init")

        def hello_invoke(func: Callable, args: tuple, kwargs: dict):
            print("before 'hello' call")
            func(*args, **kwargs)
            print("after 'hello' call")

        @decorate_fn(invoke=hello_invoke, init=hello_init)
        def hello():
            print("hello world")

    """
    def init_fn(func: Callable):
        if init is not None:
            init(func)

        @wraps(func)
        def inner_wrap(*args, **kwargs):
            return invoke(func, args, kwargs)
        return inner_wrap
    return init_fn


def unwrap_decorator_return(res: Union[Callable, Tuple[Callable, Callable]]) -> Tuple[Callable, Callable]:
    """解包装饰器方法的返回值。
    由于装饰器方法可能会返回一个方法指针或两个方法指针（返回值中init方法是可选的），
    此方法将统一返回一个长度为2的元素是方法指针的元组

    Args:
        res (Union[Callable, Tuple[Callable, Callable]]): 传入的装饰器方法返回值

    Returns:
        return_value Tuple[Callable, Callable]: invoke方法和init方法
    """
    if isfunction(res):
        return res, None
    else:
        return res[0], res[1]


def decorator_with_params(func: Callable) -> Callable:
    """这是装饰在要作为“装饰器方法”上的装饰器（装饰器的装饰器）。
    让装饰器能够拿到被装饰方法的方法签名，做一些初始化等的工作，同时大大简化了装饰器的写法

    Args:
        func (Callable): 传入的“装饰器方法”，需要返回invoke和init方法（init方法不是必须的）:
                         invoke(handle: Callable, args: list, kwargs: dict): 环切拦截器，类似Java中SpringAOP里的切面Aspect（@Around）
                         init(handle: Callable): 加载包时一次性调用的方法，你可以在这个方法里做一些初始化的工作

                         !! 重要 !! 上述的方法签名务必不要改! 哪怕你没用到handle/args/kwargs参数!


    Returns:
        inner_fn (Callable): 一个有着初始化动作（init）和调用动作（invoke）的装饰器，加强了原本的装饰器功能

    Example::

        from typing import Callable
        from decorator import decorator

        @decorator
        def hello_decorator(log_after_call: str, log_before_call: str="you're ready?"):
            def hello_init(func: Callable):
                print("do somthing with 'func', for example, register to microservice.")

            def hello_invoke(func: Callable, args: list, kwargs: dict):
                # do somthing before call
                print(log_before_call)

                # invoke the function, pass args and kwargs in
                result = func(*args, **kwargs)

                # do somthing after call
                print(log_after_call)

                # remember return the result
                return result

            return hello_invoke, hello_init

    """

    # func是否可以不带括号进行装饰
    can_without_brackets = True
    sig = signature(func)
    for first_param in sig.parameters.values():
        if first_param.kind == Parameter.POSITIONAL_OR_KEYWORD and first_param.default == Parameter.empty:
            can_without_brackets = False
        break

    if can_without_brackets and first_param.annotation == Callable:
        raise TypeError("可以进行不带括号装饰的装饰器方法的第一位参数必不能为Callable类型")

    @wraps(func)
    def inner_fn(*args, **kwargs):
        if can_without_brackets and len(args) == 1 and isfunction(args[0]):
            # 如果不带括号进行装饰，则补充括号（此处判断有一定问题，必须保证装饰器参数的第一位不是方法指针）
            wrapped_fn = args[0]
            default_args = []
            for param in signature(func).parameters.values():
                if param.default == Parameter.empty and param.kind == Parameter.POSITIONAL_OR_KEYWORD:
                    default_args.append(None)

            invoke, init = unwrap_decorator_return(func(*default_args))
            return decorate_fn(invoke, init)(wrapped_fn)
        else:
            invoke, init = unwrap_decorator_return(func(*args, **kwargs))
            return decorate_fn(invoke, init)

    setattr(inner_fn, "__wrap_func__", func)
    return inner_fn
